package com.panopset.sf;

import java.awt.Dimension;
import java.awt.Frame;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.Window;
import java.awt.event.KeyEvent;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Stack;

import javax.swing.JComponent;
import javax.swing.JFrame;

import com.google.inject.Injector;
import com.panopset.Alert;
import com.panopset.Util;
import com.panopset.Zombie;
import com.panopset.rdb.DataProps;

/**
 * Standard desktop Swing application, the basis of the swingframe project.
 *
 * @author Karl Dinwiddie
 *
 */
public abstract class App implements KeyEventDispatcher {

    /**
     * <a href="http://stackoverflow.com/questions/286727">Reference</a>.
     */
    private final KeyboardFocusManager kfm;

    /**
     * Constructor.
     */
    public App() {
        kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
        kfm.addKeyEventDispatcher(this);
    }

    @Override
    public final boolean dispatchKeyEvent(final KeyEvent e) {
        if (getTopFrame() == null) {
            return false;
        }
        getTopDoc().dispatchKeyEvent(e);
        return false;
    }

    /**
     * @return Injector.
     */
    public abstract Injector getInjector();

    /**
     * @param w
     *            Windoc.
     * @return Application component.
     */
    public abstract JComponent createWindowComp(final Windoc w);

    /**
     * @return 16x16 RezIcon path.
     */
    public abstract String getFaviconPath();

    /**
     * @return Default application window size.
     */
    public abstract Dimension getDefaultSize();

    /**
     * @return About box.
     */
    public abstract PopupBox createAboutBox();

    /**
     * About box.
     */
    private PopupBox popupBox;

    /**
     * @return about box.
     */
    public final PopupBox getAboutBox() {
        if (popupBox == null) {
            popupBox = createAboutBox();
        }
        return popupBox;
    }

    /**
     * Last x location.
     */
    public static final String APP_PROPS_KEY_LAST_XLOC = "last_xloc";

    /**
     * Last y location.
     */
    public static final String APP_PROPS_KEY_LAST_YLOC = "last_yloc";

    /**
     * Last width.
     */
    public static final String APP_PROPS_KEY_LAST_WIDTH = "last_width";

    /**
     * Last height.
     */
    public static final String APP_PROPS_KEY_LAST_HEIGHT = "last_height";

    /**
     * Application properties.
     */
    private AppProps appProps;

    /**
     * @return DataProps for global application.
     * @see ReservedTables
     * @see DataProps
     */
    public final AppProps getAppProps() {
        if (appProps == null) {
            appProps = new AppProps(this.getClass().getSimpleName()
                    .toLowerCase(), getUserBaseDirectory());
        }
        return appProps;
    }

    /**
     * Top frame.
     */
    private Windoc top;

    /**
     * @return Top frame.
     */
    public final Windoc getTopDoc() {
        if (top == null) {
            top = xref.get(getTopFrame());
            if (top == null) {
                ComponentFactory.FM.setApp(this);
                top = createWindoc();
            }
        }
        return top;
    }

    /**
     * @param f
     *            Existing windoc properties file.
     */
    public final void doOpen(final File f) {
        Windoc wd = createWindoc();
        if (wd == null) {
            return;
        }
        wd.setFile(f);
        wd.show();
    }

    /**
     * Create new windoc.
     */
    public final void doNew() {
        Windoc wd = createWindoc();
        if (wd == null) {
            return;
        }
        wd.show();
    }

    /**
     * @return new Windoc.
     */
    public final Windoc createWindoc() {
        Windoc windoc = getInjector().getInstance(Windoc.class);
        JFrame f = windoc.getFrame();
        if (f == null) {
            Alert.yellow(Util.x(

            "This application is only meant to have one screen."));
            return null;
        }
        xref.put(f, windoc);
        return windoc;
    }

    /**
     * Frame to Windoc cross reference map.
     */
    private final Map<Window, Windoc> xref = new HashMap<Window, Windoc>();

    /**
     * @return Windoc cross refrence map.
     */
    public final Map<Window, Windoc> getXref() {
        return xref;
    }

    /**
     * <a href="http://www.java2s.com/Code/Java/Swing-JFC/GetTopFrame.htm">
     * Credit</a>.
     *
     * @return Top frame.
     */
    public static Frame getTopFrame() {
        Frame[] frames = Frame.getFrames();
        for (int i = 0; i < frames.length; i++) {
            if (frames[i].getFocusOwner() != null) {
                return frames[i];
            }
        }
        if (frames.length > 0) {
            return frames[0];
        }
        return null;
    }

    /**
     * These attributes will be used for any new windows. Attributes for
     * re-opened document windows will be from those documents, not here.
     *
     * @param window
     *            Window.
     */
    public final void saveAttrs(final Window window) {
        if (window == null) {
            return;
        }
        for (Entry<Window, Windoc> e : xref.entrySet()) {
            e.getValue().doSave();
        }
    }

    /**
     * Show the top frame.
     */
    public final void show() {
        getTopDoc().show();
    }

    /**
     * 16x16 application frame icon.
     */
    private RezIcon favicon;

    /**
     *
     * @return 16x16 application frame icon.
     */
    public final RezIcon getFavicon() {
        if (favicon == null) {
            favicon = new RezIcon(getFaviconPath());
        }
        return favicon;
    }

    /**
     * 700.
     */
    public static final int DEFAULT_WIDTH = 600;

    /**
     * 500.
     */
    public static final int DEFAULT_HEIGHT = 500;

    /**
     * /com/panopset/sf/apps/branding/panopset16x16.png.
     */
    public static final String PANOPSET_ICON_PATH

    = "/com/panopset/sf/apps/branding/panopset16x16.png";

    /**
     * Application name.
     */
    private String appname;

    /**
     * @return Application name.
     */
    public final String getApplicationName() {
        if (appname == null) {
            appname = this.getClass().getSimpleName();
        }
        return appname;
    }

    /**
     *
     * @return true iff application is still running.
     */
    public final boolean isRunning() {
        return Zombie.isActive();
    }

    /**
     * Quit can be called twice, this is to make sure it doesn't execute twice.
     */
    private boolean gone = false;

    /**
     * Shutdown progress message.
     */
    protected static String msg = "";

    /**
     * Override to do additional actions before quitting.
     */
    protected void quit() {
    }

    /**
     * @param closeWindow
     *            Close window if true.
     */
    protected synchronized void shutdown() {
        if (gone) {
            return;
        }
        gone = true;
        msg = "Shutdown progress: quitting application.";
        quit();
        msg = "Shutdown progress: closing windows.";
        new Thread(new Runnable() {
            @Override
            public void run() {
                msg = "Shutdown progress: quitting application.";
                Stack<Windoc> stack = new Stack<Windoc>();
                for (Entry<Window, Windoc> e : xref.entrySet()) {
                    stack.push(e.getValue());
                }
                while (!stack.isEmpty()) {
                    stack.pop().doClose();
                }
                msg = "Shutdown progress: halting all animation.";
                Zombie.stop();
            }
        }, "Caboose").start();
        new StatusIndicator(new StatusIndicator.Caboose() {

            @Override
            public boolean isActive() {
                return Zombie.isActive();
            }

            @Override
            public String getMessage() {
                return getApplicationName() + " " + msg;
            }

        }).show();
    }

    /**
     * @return True iff the application has documents.
     */
    public final boolean hasDocuments() {
        return true;
    }

    /**
     * Theme.
     */
    private static final Theme T = new Theme();

    /**
     * @return theme.
     */
    public static final Theme getTheme() {
        return T;
    }

    /**
     * @return Application standard base directory in user directory, usually
     *         .com.something.
     */
    public abstract String getUserBaseDirectory();

}
